Magic Method 是擴展 Class 能力的一項特性,它可能是為了完成某些目的而被設計出來的。
目前 PHP 支援的 Magic Method 有以下幾種:
__construct()
/ __destruct()
__call()
/ __callStatic()
__get()
/ __set()
/ __unset()
/ __isset()
__sleep()
/ __wakeup()
__toString()
__invoke()
__set_state()
__clone()
__debugInfo()
註:所有的 Magic Method 都以
__
為前綴,除了內建的外,建議不要用__
為前綴命名 methods
註:Magic Method 必須為public function
,但如果硬是使用protected
或private
也不會報錯
__construct()
與 __destruct()
與大部份的物件導向程式語言類似,為了能在建立/銷毀物件時指定其行為,PHP 提供 __construct()
與 __destruct()
<?php
class File
{
protected $file;
public function __construct(string $filename)
{
$this->file = fopen(string $filename, 'wb');
}
public function writeString(string $input)
{
fwrite($this->file, $input);
}
public function __destruct()
{
fclose($this->file);
}
}
$file = new File('test.txt');
$file->writeString('Hello World');
自上述範例中,在 __construct()
中開啟指定資源,並在 __destruct()
中關閉該資源,這算是標準用法。
__construct()
會在 new
的時候被調用,__destruct()
則在該物件被銷毀時調用。
物件的生命周期:已經被
new
出來的物件,在函式結束時、unset()
時、沒有變數參照時、程式執行結束時、調用exit()
時都會被銷毀。
__call()
與 __callStatic()
為了能夠動態建立 Class Method 而衍生這兩個 Magic Method。
<?php
class A
{
protected $number1 = 1;
protected $number2 = 2;
protected $number3 = 3;
protected stattic $str = 'hello';
public function __call(string $name, array $args)
{
switch($name) {
case 'number1': return $this->number1;
case 'number2': return $this->number2;
case 'number3': return $this->number3;
default: return null;
}
}
public function __callStatic(string $name, array $args)
{
return self::$str;
}
}
$a = new A();
$a->number1(); // 1
$a->number2(); // 2
$a->number3(); // 3
$a->number4(); // null
A::foo(); // hello
A::bar(); // hello
註:在 Laravel 中,有許多神奇的功能都是倚靠這個 Magic Method 實現的。
__get()
, __set()
, __isset()
及 __unset()
__get()
會被調用__set()
會被調用isset()
或 empty()
時,__isset()
會被調用unset()
時,__unset()
會被調用「不存在或不可見的值」,表示為在目前的作用域無法存取到的 attributes,可能是沒有被宣告到,或是在外部存取 protected
或 private
的值。
當時這些 Magic Method 的設計背景應該是為了簡化 Java 的 Getter 及 Setter 模式,但因為大部份的 IDE 都無法正常解析,在正常撰寫程式時通常不會使用。
註:不過 Laravel 裡大量用到這些功能,事實上在適度的封裝後它是好用的,惟效能上較低落(經過簡單實測,大概差 9 倍效率)
__sleep()
及 __wakeup()
在談這兩個 Magic Method 之前,需要先介紹兩個函式:serialize()
及 unserialize()
。
serialize()
可以將資料轉換為字串,除了以下兩種資料無法轉換外,其餘都可以轉為可儲存的字串:
__sleep()
會在 serialize()
被呼叫時調用,用於清理一些無法儲存的資料(例如 resource),或是斷開資料庫連線等等。
__wakeup()
會在 unserialize()
被呼叫時調用,用於重新建立部份資料,或是重新連接資料庫。
值得注意的是,如果 Class 實現了 Serializable
的話,__sleep()
及 __wakeup()
將不再有效,會被 public function serialize()
及 public function unserialize()
取代。
__toString()
這個 Magic Method 會在 Class 被轉型為 string 時呼叫,例如被 echo
或 print
時應該回應什麼。
class Stringable
{
protected $str = 'Hello';
public function __toString()
{
return $this->str;
}
}
$s = new Stringable();
echo $s; // Hello
__invoke()
這個 Magic Method 會在 Class 被當成函式時應該如何執行。
class Invokable
{
public function __invoke(string $name = 'World')
{
echo "Hello $name";
}
}
(new Invokable)(); // Hello World
(new Invokable)('Jack'); // Hello Jack
__clone
這個 Magic Method 會在對 Class 使用 clone
時被調用。
例如有時複製一個資源,但希望 ID 能夠有所變化:
<?php
class User
{
public static $id = 1;
public function __clone()
{
return ++self::$id;
}
}
$user1 = new User;
$user2 = clone $user1;
$user1::$id; // 1
$user2::$id; // 2
__debugInfo()
這個 Magic Method 可以修改 var_dump()
及 print_r()
的顯示結果。
<?php
class A
{
private $a = 10;
}
class B
{
private $a = 10;
public function __debugInfo()
{
return ['a' => 123];
}
}
var_dump(new A);
// object(A)#2355 (1) {
// ["a":"A":private] =>
// int(10)
// }
var_dump(new B);
// object(B)#2380 (1) {
// ["a"]=>
// int(123)
// }
有一個 Magic Method 被我自動忽略了。
__set_state()
,因為它的行為是真的詭異。
官方文件表示它是與 var_export()
做搭配,var_export()
與 var_dump()
的差異在於 var_export()
會回傳符合 PHP 語法的字串,可以搭配 eval()
運用。
不過我真的想不到有哪裡會用這種奇怪的寫法,所以 __set_state()
就自動被我忽略了。